#include <iostream>

#include <boost/type_traits/conditional.hpp>
#include <boost/type_traits/has_plus_assign.hpp>
#include <boost/type_traits/has_plus.hpp>
#include <boost/type_traits/has_post_increment.hpp>
#include <boost/type_traits/has_pre_increment.hpp>
#include <boost/mpl/if.hpp>

namespace detail {
	struct pre_inc_functor {
		template <class T>
		void operator()(T& value) const {
			++value;
			std::cout << "pre_inc_functor" << std::endl;
		}
	};
	
	struct post_inc_functor {
		template <class T>
		void operator()(T& value) const {
			value++;
			std::cout << "post_inc_functor" << std::endl;
		}
	};

	struct plus_assignable_functor {
		template <class T>
		void operator()(T& value) const {
			value += T(1);
			std::cout << "plus_assignable_functor" << std::endl;
		}
	};

	struct plus_functor {
		template <class T>
		void operator()(T& value) const {
			value = value + T(1);
			std::cout << "plus_functor" << std::endl;
		}
	};
}

template <class T>
void inc(T& value) {
	typedef detail::plus_functor step_0_t;

	typedef typename boost::conditional<
		boost::has_plus_assign<T>::value,
		detail::plus_assignable_functor,
		step_0_t
	>::type step_1_t;

	typedef typename boost::conditional<
		boost::has_post_increment<T>::value,
		detail::post_inc_functor,
		step_1_t
	>::type step_2_t;

	typedef typename boost::conditional <
		boost::has_pre_increment<T>::value,
		detail::pre_inc_functor,
		step_2_t
	>::type step_3_t;

	step_3_t()(value);
}

template <class T>
void inc_mpl(T& value) {
	typedef detail::plus_functor step_0_t;

	typedef typename boost::mpl::if_<
		boost::has_plus_assign<T>,
		detail::plus_assignable_functor,
		step_0_t
	>::type step_1_t;

	typedef typename boost::mpl::if_<
		boost::has_post_increment<T>,
		detail::post_inc_functor,
		step_1_t
	>::type step_2_t;

	typedef typename boost::mpl::if_<
		boost::has_pre_increment<T>,
		detail::pre_inc_functor,
		step_2_t
	>::type step_3_t;

	step_3_t()(value);
}

class test_pre_inc
{
public:
	int a = 0;
	test_pre_inc& operator++() {
		++a;
		return *this;
	}
};

class test_post_inc
{
public:
	int a = 0;
	test_post_inc& operator++(int) {
		a++;
		return *this;
	}
};

class test_assignable_functor
{
public:
	test_assignable_functor() = default;
	test_assignable_functor(int param) : a(param)
	{}

	int a = 0;
	test_assignable_functor& operator+=(const test_assignable_functor& param) {
		a += param.a;
		return *this;
	}
};

class test_plus_functor
{
public:
	test_plus_functor() = default;
	test_plus_functor(int param) : a(param)
	{}

	int a = 0;
	test_plus_functor operator+(const test_plus_functor& param) {
		test_plus_functor temp;
		temp.a = a + param.a;
		return temp;
	}
};

int main()
{
	int a = 1;
	inc(a);
	std::cout << "==== 1 ====" << std::endl
		<< "a:" << a << "    " << std::endl;

	test_pre_inc b;
	inc(b);
	std::cout << "==== 2 ====" << std::endl
		<< "b.a:" << b.a << "    " << std::endl;

	test_post_inc c;
	inc(c);
	std::cout << "==== 3 ====" << std::endl
		<< "c.a:" << c.a << "    " << std::endl;

	test_assignable_functor d;
	inc(d);
	std::cout << "==== 4 ====" << std::endl
		<< "d.a:" << d.a << "    " << std::endl;

	test_plus_functor e;
	inc(e);
	std::cout << "==== 5 ====" << std::endl
		<< "e.a:" << e.a << "    " << std::endl;

	inc(b);
	std::cout << "==== 6 ====" << std::endl
		<< "b.a:" << b.a << "    " << std::endl;

	inc(c);
	std::cout << "==== 7 ====" << std::endl
		<< "c.a:" << c.a << "    " << std::endl;

	inc(d);
	std::cout << "==== 8 ====" << std::endl
		<< "d.a:" << d.a << "    " << std::endl;

	inc(e);
	std::cout << "==== 9 ====" << std::endl
		<< "e.a:" << e.a << "    " << std::endl;

	return 0;
}